绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型。但是有些情况,我们无法确定对象中有哪些属性(或者说对象中只有一部分属性可以确定,但是其余的属性不能确定,我们在使用的时候会添加属性),此时,就需要使用索引签名类型了。
x1interface AnyObject {2 [key: string]: number3}45let obj: AnyObject = {6 a: 1,7 b: 2,8}
因为JS中对象的键是 string 类型的,所以[key:string]可以表示对象中的所有键。包括函数哦,不要忘了,对象中也可以定义函数,函数名就是key,也是string类型。
xxxxxxxxxx1212interface AnyObject {3 [key: string]: number | (() => void)4}56let obj: AnyObject = {7 a: 1,8 b: 2,9 f() {10 console.log("1");11 }12}
在JS中,数组是一类特殊的对象,特殊的地方:数组的键(索引)是数值类型。并且,数组中可以出现任意多个元素。所以在数组对应的泛型接口中,也用到了索引签名类型。比如说下面我们自己定义一个数组类型。
xxxxxxxxxx61interface MyArray<T> {2 [n: number]: T3}45let arr: MyArray<number> = [1, 3, 5]6console.log(arr[0]);
作用:基于旧的类型,创建新的类型(对象类型),减少重复,提升开发效率。
核心思想是:“循环遍历原类型的所有属性,对每个属性做统一的类型转换,最终得到新类型”,就像用 “模板” 批量处理类型,避免重复写冗余代码。
核心语法:{ [P in K]: T}
在这里的in表示遍历,[P in K]遍历逻辑:P 是 “当前属性名”(占位符,可自定义),K 是 “要遍历的属性集”(通常使用 keyof 原类型来生成属性集),将遍历到的属性 P 的类型,改为 T。
in在 TS 中的核心含义的是 “遍历” 或 “存在判断”,对应两个核心场景:
- 映射类型中:
[P in K]→ 遍历 K 中的所有属性(最常用);- 类型守卫中:
属性 in 对象→ 判断属性是否存在,缩小类型范围。

在老师的例子中,PropKeys就是属性集。
上一个例子中,PropKeys是联合类型,但最常见的就是从一个对象类型里面获取属性集,这时候就要使用keyof来获取所有键的联合类型。

xxxxxxxxxx121// 自己实现 Partial 的功能2type IPartial<T> = {3 [key in keyof T]?: T[key]4}56type Props = {7 a: number;8 b: string;9 c: boolean;10}1112type PartialProps = IPartial<Props>;


可以在[]里面使用联合类型或者keyof操作符,来查询多个索引的类型。

在学习Material UI的时候,在最后一节Custumize Theme里面,新增了一些样式,但是新增的样式不在MUI定义的ts类型里面,这时候怎么解决呢?老师新增了一个
src/theme.d.ts文件,在里面写了一些ts的代码,但这些代码是怎么起作用的,我是完全没有头绪。xxxxxxxxxx291import { Theme, ThemeOptions } from '@mui/material/styles'23declare module '@mui/material/styles' {4interface Theme {5status: {6danger: string7}8}9interface ThemeOptions {10status: {11danger: React.CSSProperties['color']12}13}1415interface Palette {16neutral?: PaletteColor17}18interface PaletteOptions {19neutral?: PaletteColorOptions20}2122interface PaletteColor {23darker?: string24}2526interface SimplePaletteColorOptions {27darker?: string28}29}我的疑问就是:这种写法真的不会覆盖原来定义的ts类型吗?但是看实际的效果,我觉得是为这些interface新增了属性类型。但是怎么实现的,我心里总是没底,所以干脆学习一下,以后肯定能够用到的。
这里面用到了“声明合并”的功能,https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation这里可以参考。其实了解清楚了就非常简单,比如:
xxxxxxxxxx111// 在 node_modules/some-lib/index.d.ts 中2export interface Options {3debug: boolean;4}56// 在你自己的 xxx.d.ts 文件中7declare module 'some-lib' {8interface Options {9verbose?: boolean;10}11}➡️ TypeScript 会把你的定义与原始模块
some-lib的定义合并。 之后Options类型包含debug与verbose。


当在.d.ts文件中写可执行代码的时候,会报错:


在全局上下文中不能声明实现。



现在绝大多数库都提供自带的类型声明文件,这个地址提供的只是很少的一部分。注意:TS官方文档中提供的页面,现在已经404了。

在实际使用中,需要自己创建类型声明文件有两种情况:1、项目内需要共享类型;2、为已有的JS文件提供类型声明。

这个也分为2部分。


这个内容还是蛮重要的,我在react项目、nextjs项目、nestjs项目中,都可以新建一个index.d.ts文件,在这个文件里面写项目需要的ts类型,避免临时写类型,这样代码就不好看。如果是多个人共同协作的项目,编写类型声明文件的时候,就可以使用拆分。
🧩 一、类型声明文件为什么容易冲突
当多人维护一个大的
global.d.ts或types.d.ts文件时:
- 很容易出现重复声明(比如同一个接口不同人扩展);
- 命名空间冲突;
- 不同模块之间类型污染;
- Git merge 时产生冲突。
👉 解决思路:让类型声明模块化、可组合化。
📁 二、正确的拆分方式
✅ 方式 1:基于功能模块拆分
这是最推荐的方式,类似源码模块化。
xxxxxxxxxx71src/2├── types/3│ ├── user.d.ts // 用户模块相关类型4│ ├── product.d.ts // 商品模块相关类型5│ ├── api.d.ts // 接口请求/响应6│ ├── theme.d.ts // 样式/主题扩展(如 MUI)7│ └── index.d.ts // 汇总入口index.d.ts
xxxxxxxxxx41/// <reference path="./user.d.ts" />2/// <reference path="./product.d.ts" />3/// <reference path="./api.d.ts" />4/// <reference path="./theme.d.ts" />或使用 import/export 聚合(如果是模块化声明):
xxxxxxxxxx41// types/index.ts2export * from './user';3export * from './product';4export * from './api';⚠️ 注意:如果项目用
"typeRoots"或"include"指定了类型路径, 需要确保tsconfig.json包含这些文件。✅ 方式 2:使用
declare module拆分如果是对第三方库(如 MUI、Express、Axios)进行扩展,可以每个库单独一个文件:
xxxxxxxxxx191// types/mui-theme.d.ts2import '@mui/material/styles';34declare module '@mui/material/styles' {5interface Theme {6customColor: string;7}8interface ThemeOptions {9customColor?: string;10}11}12// types/axios.d.ts13import 'axios';1415declare module 'axios' {16interface AxiosRequestConfig {17retryCount?: number;18}19}这样每个模块都独立声明、自动合并,不会冲突。
✅ 方式 3:为全局声明定义独立命名空间
如果必须写全局类型(如全局变量、工具函数),建议用命名空间隔离:
xxxxxxxxxx91// types/global-utils.d.ts2declare namespace AppUtils {3function formatDate(date: Date): string;4interface ApiResponse<T> {5code: number;6data: T;7msg: string;8}9}使用时:
xxxxxxxxxx11const data: AppUtils.ApiResponse<User> =这样不会污染全局命名空间。
🧠 三、多人协作时的规范建议
问题 解决方案 不同人改同一文件 拆分到独立的模块 .d.ts文件命名重复 统一命名空间或类型前缀,例如 UserInfo,UserApiResponse类型引用混乱 建立统一的 types/index.ts导出入口版本不一致 在 PR 审查阶段严格检查类型变更影响 类型测试 使用 tsd或vitest+expectTypeOf做类型测试📦 四、tsconfig.json 配置示例
xxxxxxxxxx111{2"compilerOptions": {3"baseUrl": ".",4"paths": {5"@types/*": ["src/types/*"]6},7"typeRoots": ["src/types", "node_modules/@types"],8"strict": true9},10"include": ["src", "src/types"]11}✅ 小结
做法 说明 按功能模块拆分 .d.ts最基础、最有效的防冲突方式 使用 declare module扩展三方类型自动声明合并,安全 使用命名空间隔离全局类型 避免污染 建立统一 types/index.ts出口便于统一管理和导入 tsconfig 明确 typeRoots路径确保编译器能识别
为已有JS文件提供类型声明,这个很简单,就是使用declare关键字来将类型声明一下,然后使用es6提供的模块化方案来导出定义好的类型,最后在文件里面引入并使用即可。

https://www.typescriptlang.org/docs/handbook/declaration-merging.html
extends 继承接口xxxxxxxxxx141interface Person {2 name: string;3 age: number;4}56interface Employee extends Person {7 employeeId: string;8}910const e: Employee = {11 name: "Tom",12 age: 25,13 employeeId: "A123"14};✅
extends类似于「面向对象的继承」: 子接口拥有父接口的所有属性,并可以新增自己的。
& 合并xxxxxxxxxx111type Person = { name: string; age: number; };2type Contact = { email: string; phone: string; };34type PersonWithContact = Person & Contact;56const user: PersonWithContact = {7 name: "Alice",8 age: 30,9 email: "a@example.com",10 phone: "123456"11};💡
&的效果类似“交集”,结果类型需要同时满足左右两边的结构。 常用于“组合多个类型”或“给库类型补充属性”。
🔥 这是在实际项目里非常常见的一种「类型扩展」。
xxxxxxxxxx111// 文件:types/mui-theme.d.ts2import "@mui/material/styles";34declare module "@mui/material/styles" {5 interface Theme {6 customColor: string;7 }8 interface ThemeOptions {9 customColor?: string;10 }11}然后在组件中:
xxxxxxxxxx41import { useTheme } from "@mui/material/styles";23const theme = useTheme();4console.log(theme.customColor);⚙️ TypeScript 会自动“合并”同名
declare module的接口声明。
declare global)xxxxxxxxxx81// types/global.d.ts2export {};34declare global {5 interface Window {6 myAppVersion: string;7 }8}这样在代码中你可以直接使用:
xxxxxxxxxx11console.log(window.myAppVersion);💡
declare global适合扩展环境对象(Window,NodeJS.ProcessEnv等)。
xxxxxxxxxx61function getName<T extends { name: string }>(obj: T) {2 return obj.name;3}45getName({ name: "Bob", age: 20 }); // OK6getName({ age: 20 }); // ❌ 缺少 name这里的
extends表示“T 必须至少包含 name”。
有时我们还会通过工具类型进行“选择性扩展”:
xxxxxxxxxx91interface User {2 id: number;3 name: string;4 email: string;5 age: number;6}78// 从 User 中选取部分字段再扩展9type UserPreview = Pick<User, "id" | "name"> & { active: boolean };| 概念 | 写法 | 适用场景 | 是否创建新类型 |
|---|---|---|---|
继承 (extends) | interface B extends A | 派生新接口 | ✅ 是 |
交叉类型 (&) | type C = A & B | 组合类型 | ✅ 是 |
| 声明合并 | declare module / 同名接口 | 扩展已有类型(特别是库) | ❌ 否(原地合并) |
全局扩展 (declare global) | interface Window {} | 为环境补充类型 | ❌ 否 |
假设你想给 MUI Theme 增加一个 status 颜色:
xxxxxxxxxx151// types/mui-theme.d.ts2import '@mui/material/styles';34declare module '@mui/material/styles' {5 interface Theme {6 status: {7 danger: string;8 };9 }10 interface ThemeOptions {11 status?: {12 danger?: string;13 };14 }15}在主题定义里:
xxxxxxxxxx51const theme = createTheme({2 status: {3 danger: "#e53e3e"4 }5});现在 TS 就能识别 theme.status.danger 的类型了 ✅。
| 目标 | 推荐做法 |
|---|---|
| 扩展自己的类型 | 使用 extends 或 & |
| 扩展第三方库 | 使用 declare module "xxx" |
| 扩展全局对象 | 使用 declare global |
| 组合类型 | 使用交叉类型 & |
| 多人维护时避免冲突 | 拆分不同 .d.ts 文件独立扩展 |
https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases
类型别名,就是给类型起一个别名,让它可以更方便的被重用。使用 type 关键字。
xxxxxxxxxx251let sum: (x: number, y: number) => number2/* 上面是定义接口的简写,完整写法如下:3interface ISum{4 (x:number,y:number): number5}6let sum: ISum7*/89const result = sum(1,2)101112type PlusType = (x: number, y: number) => number13let sum2: PlusType = (x, y) => {14 return x +y;15}1617// 支持联合类型18type StrOrNumber = string | number19let result2: StrOrNumber = '123'20result2 = 1232122// 字符串字面量,然后赋值操作的时候,变量值只能在字面量里面选择,不能赋别的值。23// 这个看上去和枚举很像,能不能用枚举来做呢?我试过了,不能,那枚举到底有什么作用呢?它到底是一种类型还是什么呢?24type Directions = 'Up' | 'Down' | 'Left' | 'Right'25let toWhere: Directions = 'Up'这样就产生了一个疑问:type和interface的区别是什么?哪里应该用type,哪里应该用interface?
先搞简单点:
Almost all features of an
interfaceare available intype, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.主要区别是:
- 接口是通过继承(extends)的方式来扩展,类型别名是通过 & 来扩展。
- 同名接口可以自动合并,而类型别名不行(会报错)
上面的“接口通过继承的方式来扩展”,在interface这一节中并没有专门讲,但是通过泛型与接口已经讲到了,那么能不能定义一个接口,里面使用interface的extends来扩展呢?
可以这样写:
要结合泛型一起使用才行:
https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#intersection-types
交叉类型将多种类型组合为一种。这使您可以将现有类型加在一起,以获得具有所需所有功能的单个类型。
xxxxxxxxxx61interface IName {2 name: string3}45type IPerson = IName & { age: number }6let person: IPerson = { name: 'hello', age: 12}
操作DOM和BOM时,一定要为变量定义类型,不然定义的事件都不能使用。也不要太担心,应该会有代码提示的,如果没有代码提示,可以console.dir(变量名),来查看这个变量是什么类型。
xxxxxxxxxx181const a: Array<number> = [1,2,3]2// 大家可以看到这个类型,不同的文件中有多处定义,但是它们都是 内部定义的一部分,然后根据不同的版本或者功能合并在了一起,一个interface 或者 类多次定义会合并在一起。这些文件一般都是以 lib 开头,以 d.ts 结尾,告诉大家,我是一个内置对象类型3const date: Date = new Date()4const reg = /abc/5// 我们还可以使用一些 build in object,内置对象,比如 Math 与其他全局对象不同的是,Math 不是一个构造器。Math 的所有属性与方法都是静态的。67Math.pow(2,2)89// DOM 和 BOM 标准对象10// document 对象,返回的是一个 HTMLElement11let body: HTMLElement = document.body12// document 上面的query 方法,返回的是一个 nodeList 类型13let allLis = document.querySelectorAll('li')1415//当然添加事件也是很重要的一部分,document 上面有 addEventListener 方法,注意这个回调函数,因为类型推断,这里面的 e 事件对象也自动获得了类型,这里是个 mouseEvent 类型,因为点击是一个鼠标事件,现在我们可以方便的使用 e 上面的方法和属性。16document.addEventListener('click', (e) => {17 e.preventDefault()18})Typescript 还提供了一些功能性,帮助性的类型,这些类型,大家在 js 的世界是看不到的,这些类型叫做 utility types,提供一些简洁明快而且非常方便的功能。
xxxxxxxxxx161// partial,它可以把传入的类型都变成可选2interface IPerson {3 name: string4 age: number5}67let viking: IPerson = { name: 'viking', age: 20 }89type IPartial = Partial<IPerson>10let viking2: IPartial = { }111213// Omit,它返回的类型可以忽略传入类型的某个属性,这个在使用类型时应该用得到,如果你想忽略某些属性的话。1415type IOmit = Omit<IPerson, 'name'>16let viking3: IOmit = { age: 20 }| 关键字 | 含义 | 示例 |
|---|---|---|
type | 定义类型别名 | ts type Point = { x: number; y: number }; const p: Point = { x: 10, y: 20 }; |
interface | 定义接口结构(可被类实现或扩展) | ts interface Person { name: string; age: number; } const p: Person = { name: 'Tom', age: 20 }; |
implements | 类实现接口 | ts interface Flyable { fly(): void; } class Bird implements Flyable { fly() { console.log('flying'); } } |
enum | 枚举类型 | ts enum Direction { Up, Down, Left, Right } const dir = Direction.Up; |
readonly | 只读属性 | ts interface User { readonly id: number; name: string; } const u: User = { id: 1, name: 'Tom' }; // u.id = 2 ❌ |
keyof | 获取类型的键名集合 | interface User { id: number; name: string; } type Keys = keyof User; // 'id' |
typeof | 获取变量或类型的类型 | ts const user = { name: 'Tom', age: 20 }; type UserType = typeof user; |
infer | 条件类型中推断类型 | ts type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type R = ReturnType<() => string>; // string |
is | 自定义类型守卫 | ts function isString(x: unknown): x is string { return typeof x === 'string'; } |
as | 类型断言 | ts const value: unknown = 'abc'; const len = (value as string).length; |
satisfies | 检查对象是否满足特定类型(不改变推断) | 下面有 |
in | 用于类型映射或枚举属性 | 下面有 |
extends | 泛型约束或类继承 | ts function logLength<T extends { length: number }>(arg: T) { console.log(arg.length); } |
abstract | 定义抽象类或方法 | ts abstract class Shape { abstract area(): number; } class Circle extends Shape { constructor(private r: number) { super(); } area() { return Math.PI * this.r ** 2; } } |
declare | 声明全局变量/模块/类型(不生成 JS 代码) | ts declare const VERSION: string; // 告诉 TS 存在一个全局变量 VERSION |
namespace | 定义命名空间(旧式模块组织方式) | ts namespace MathUtil { export const PI = 3.14; export function square(x: number) { return x * x; } } console.log(MathUtil.square(2)); |
never | 不会返回的类型 | ts function fail(msg: string): never { throw new Error(msg); } |
unknown | 不确定但安全的类型(比 any 更安全) | ts let x: unknown = 'hello'; if (typeof x === 'string') console.log(x.toUpperCase()); |
any | 任意类型(跳过类型检查) | ts let value: any = 42; value = 'hi'; value.toFixed(); |
void | 无返回值类型 | ts function log(msg: string): void { console.log(msg); } |
🧩 一、
satisfies— “类型符合检查”(TS 4.9+)✅ 用途
satisfies用来验证某个值是否满足指定类型约束,但 不会改变 该值的推断类型。 这比as更安全(as是强制断言,satisfies是类型验证)。✅ 示例 1:验证对象是否满足接口要求
xxxxxxxxxx141type ApiConfig = {2url: string;3method: 'GET' | 'POST';4};56const config = {7url: '/api/user',8method: 'GET',9headers: { Authorization: 'Bearer token' },10} satisfies ApiConfig;1112// ✅ 编译通过,因为 config 满足 ApiConfig13// ✅ 推断类型仍保留 headers 属性14console.log(config.headers.Authorization);✅
satisfies不会把config的类型缩窄成ApiConfig, 仍然保留了额外属性(如headers),仅验证其“满足”接口要求。⚠️ 对比
asxxxxxxxxxx81const config2 = {2url: '/api/user',3method: 'GET',4headers: { Authorization: 'Bearer token' },5} as ApiConfig;67// ⚠️ 此时 headers 丢失了类型信息8// config2.headers.Authorization ❌ 报错:Property 'headers' does not exist✅ 示例 2:校验文字类型而不丢失推断
xxxxxxxxxx111type Theme = {2color: 'light' | 'dark';3fontSize: number;4};56const theme = {7color: 'dark',8fontSize: 14,9} satisfies Theme;1011// theme.color 推断为 "dark"(字面量),而不是 string✅
satisfies在这里让 TypeScript 保留了字面量类型"dark", 而不是自动放宽为string,这有助于类型安全。🧮 二、
in— “映射类型”或“属性遍历”✅ 用途
**in**用在类型定义里,表示遍历联合类型的每个成员。 通常与索引签名结合,构造新类型。✅ 示例 1:从键集合生成类型
xxxxxxxxxx121type Keys = 'id' | 'name' | 'email';23type User = {4[K in Keys]: string;5};67// 等价于:8// type User = {9// id: string;10// name: string;11// email: string;12// }✅ 示例 2:生成布尔标志类型
xxxxxxxxxx71type Permissions = 'read' | 'write' | 'execute';23type PermissionFlags = {4[P in Permissions]: boolean;5};67// => { read: boolean; write: boolean; execute: boolean }✅ 示例 3:条件映射 + in
xxxxxxxxxx111type Nullable<T> = {2[K in keyof T]: T[K] | null;3};45interface Person {6name: string;7age: number;8}910type NullablePerson = Nullable<Person>;11// => { name: string | null; age: number | null }✅ 示例 4:
in也能用于循环(运行时)⚠️ 注意:这里的
in是 JS 层面的,不同于上面的类型用法。xxxxxxxxxx41const user = { id: 1, name: 'Tom' };2for (const key in user) {3console.log(key, user[key as keyof typeof user]);4}✅ 总结对比表
关键字 作用层面 用途 是否影响推断类型 satisfies类型检查阶段 检查值是否满足某个类型,但不改变类型推断 ❌ 不会改变 in类型定义(映射类型) 遍历联合类型的成员生成结构化类型 ✅ 会生成新类型结构
| 分类 | 描述 |
|---|---|
| 类型定义 | type, interface, enum |
| 类型操作 | keyof, typeof, infer, extends, in, is, as, satisfies |
| 修饰符 | readonly, abstract, implements |
| 声明用途 | declare, namespace |
| 类型级别 | any, unknown, never, void |
xxxxxxxxxx351// 定义接口和类型别名2interface User {3 readonly id: number;4 name: string;5}67type ApiResponse<T> = {8 data: T;9 error?: string;10};1112// 泛型 + 约束 + infer + keyof13function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {14 return obj[key];15}1617const user: User = { id: 1, name: "Alice" };18const userName = getProperty(user, "name"); // string1920// 条件类型 + infer21type PromiseValue<T> = T extends Promise<infer U> ? U : T;22type A = PromiseValue<Promise<number>>; // number2324// 自定义类型守卫25function isNumber(x: unknown): x is number {26 return typeof x === "number";27}2829// satisfies + typeof30const config = {31 url: "/api",32 method: "GET",33} satisfies { url: string; method: "GET" | "POST" };3435console.log(config.url);✅ TypeScript 是 JavaScript 的超集, 所以它 包含了全部 JS 的符号, 并且 额外增加了一些类型系统专用符号(如
:,?,|,&,as,!,<>等)。
换句话说:
| 分类 | 符号 | 示例 |
|---|---|---|
| 算术运算符 | + - * / % ** | a + b |
| 递增/递减 | ++ -- | i++ |
| 赋值运算符 | = += -= *= /= %= **= | x += 5 |
| 比较运算符 | == != === !== > < >= <= | a === b |
| 逻辑运算符 | && || ! | a && b |
| 位运算符 | & | ^ ~ << >> >>> | a << 2 |
| 条件运算符 | ?: | a > 0 ? 1 : 0 |
| 展开与剩余 | ... | { ...obj } / (...args) |
| 对象与数组解构 | {} [] | const {x, y} = p |
| 模板字符串 | ${} | Hello ${name} |
| 可选链 | ?. | obj?.a?.b |
| 空值合并 | ?? | value ?? 'default' |
| new / delete / typeof / instanceof / in | 与 JS 相同 | typeof x === 'string' |
这些是 TypeScript 完全继承的符号。 TS 对这些符号的 运行时语义 与 JS 完全一致。
| 符号 | 含义 | 示例 | JS 中是否存在 |
|---|---|---|---|
: | 声明类型 | let x: number = 5; | ❌ |
? | 可选属性 / 参数 | interface User { name?: string } | ❌ |
| | 联合类型(或) | let id: string | number; | |
& | 交叉类型(与) | type A = B & C; | ❌ |
<T> | 泛型声明 | function foo<T>(arg: T) {} | ❌ |
as | 类型断言 | value as string | ❌(JS 中仅语法无效) |
! | 非空断言 | user!.name | ❌ |
?: | 映射类型中的可选修饰 | { [K in Keys]?: string } | ❌ |
-? | 映射类型中的移除可选修饰 | { [K in Keys]-?: string } | ❌ |
keyof | 取类型的键名 | type K = keyof User | ❌ |
typeof | 取类型(不同于 JS 的 typeof) | type T = typeof obj | ✅(语义不同) |
infer | 在条件类型中推断类型 | T extends infer U ? U : never | ❌ |
extends | 类型约束 / 类继承 | <T extends object> | ✅(类继承同名,但语义扩展) |
in | 用于映射类型 | { [K in keyof T]: T[K] } | ✅(语义扩展) |
is | 类型守卫 | x is string | ❌ |
satisfies | 类型符合检查 | obj satisfies Config | ❌ |
readonly | 只读修饰符 | readonly name: string | ❌ |
abstract | 抽象类/方法 | abstract class Shape | ✅(ES2022+ 才支持) |
implements | 实现接口 | class A implements B {} | ❌ |
declare | 声明存在的全局变量/模块 | declare const VERSION: string; | ❌ |
namespace | 命名空间 | namespace MathUtil { ... } | ❌ |
=> | 箭头函数 | (a: number) => a + 1 | ✅(JS 有) |
: —— 类型注解xxxxxxxxxx41let name: string = 'Tom';2function sum(a: number, b: number): number {3return a + b;4}
JS 中
:没有此语法。
| & & —— 联合与交叉类型xxxxxxxxxx21type ID = string | number;2type User = { name: string } & { age: number };
JS 中
|与&仅用于位运算, TS 中用于类型组合。
as —— 类型断言xxxxxxxxxx11const el = document.getElementById('btn') as HTMLButtonElement;
JS 没有类型系统,所以
as无意义。
! —— 非空断言xxxxxxxxxx31function greet(name?: string) {2console.log(name!.toUpperCase());3}
告诉编译器 “我确定这里不是 null/undefined”。 JS 仅有逻辑非
!,语义不同。
<T> —— 泛型声明xxxxxxxxxx31function identity<T>(arg: T): T {2return arg;3}
JS 中
< >仅用于比较运算。 TS 中<T>表示类型参数。
keyof / in / extendsxxxxxxxxxx31type Person = { name: string; age: number };2type Keys = keyof Person; // 'name' | 'age'3type ReadonlyPerson = { [K in keyof Person]: Readonly<Person[K]> };
这些符号仅存在于 TS 类型层级中。
satisfiesxxxxxxxxxx21type Config = { url: string; method: 'GET' | 'POST' };2const cfg = { url: '/api', method: 'GET' } satisfies Config;
JS 无法进行这种类型验证。
| 分类 | JavaScript | TypeScript 新增 / 扩展 |
|---|---|---|
| 运行时符号 | `+ - * / % && | |
| 类型声明 | 无 | :、?、` |
| 类型操作 | 无 | keyof、typeof(扩展)、infer、extends、in、is、satisfies |
| 修饰符 | 无 | readonly、abstract、implements、declare、namespace |
学了第二章的内容,发现TS为各种数据类型做了约束,这不就是C或JAVA语言里面的规范,怎么又加进来了吗?
TS中规定了很多类型,好处就是在我写ts代码的时候,必须按照定义的类型来写,不然在编写代码的时候就会报错,就是在写代码的时候就告诉我,这个地方要注意。
这在写别的代码的时候都没有的情况,别的代码写出来之后,最多检查一下语法规范什么的,不会对类型这方面的东西给出提示,但是ts中开启了监视功能之后,很快就能给出提示。不会出现一种数据类型使用另外一种数据类型方法的错误,而JS只有在执行之后才能告诉我这种错误。保持了代码的一致性,避免JS那种找不到错误的情况。